﻿using System;
using System.Text;
using LeadTracker.Models;
using System.Security.Cryptography;
using Granite.Data;
using System.Collections.Generic;

namespace LeadTracker.DataAccess
{
	public class UserRepository
	{
		readonly Connection m_Connection;

		public UserRepository(Connection connection)
		{
			m_Connection = connection;
		}

		public void CreateUser(EditorModel newUser, string password)
		{
			if (!newUser.Validate())
				throw new ArgumentException("newUser failed validation", "newUser");

			using (var trans = Connection.BeginTransaction(true))
			{

				var cmd1 = new ProcedureCall("dbo.EditorFindKey");
				cmd1.Add("UserName", newUser.UserName);
				var userNameKey = trans.ExecuteScalar<int>(cmd1);
				if (userNameKey.HasValue)
					throw new CreateUserException(CreateUserError.DuplicateUserName);

				var cmd2 = new ProcedureCall("dbo.EditorFindKey");
				cmd2.Add("EmailAddress", newUser.EmailAddress);
				var emailAddressKey = trans.ExecuteScalar<int>(cmd2);
				if (emailAddressKey.HasValue)
					throw new CreateUserException(CreateUserError.DuplicateEmailAddress);

				var passwordSalt = CreatePasswordSalt();
				var passwordHash = ComputeHash(password, passwordSalt);

				var cmd3 = new ProcedureCall("dbo.EditorInsert");
				cmd3.Add("UserName", newUser.UserName);
				cmd3.Add("FirstName", newUser.FirstName);
				cmd3.Add("LastName", newUser.LastName);
				cmd3.Add("EmailAddress", newUser.EmailAddress);
				cmd3.Add("PasswordSalt", passwordSalt);
				cmd3.Add("PasswordHash", passwordHash);

				newUser.Update(trans.ExecuteRow(cmd3));

				trans.Commit();
			}

		}


		public bool ValidateUser(string userName, string password)
		{
			var cmd = new ProcedureCall("EditorPasswordSelect");
			cmd.Add("UserName", userName);

			var passwordInfo = Connection.ExecuteRow(cmd, ExecuteRowOptions.AllowEmptyResults);
			if (passwordInfo == null)
				return false;

			var salt = (string)passwordInfo["PasswordSalt"];
			var expectedHash = (string)passwordInfo["PasswordHash"];

			string hash = ComputeHash(password, salt);

			return hash == expectedHash;
		}

		public void SetPassword(string userName, string newPassword)
		{
			var newSalt = CreatePasswordSalt();
			var newHash = ComputeHash(newPassword, newSalt);
			var cmd = new ProcedureCall("EditorPasswordUpdate");
			cmd.Add("UserName", userName);
			cmd.Add("PasswordHash", newHash);
			cmd.Add("PasswordSalt", newSalt);

			Connection.ExecuteRow(cmd);
		}

		public bool ChangePassword(string userName, string oldPassword, string newPassword)
		{
			if (!ValidateUser(userName, oldPassword))
				return false;

			SetPassword(userName, newPassword);

			return true;
		}

		static string CreatePasswordSalt()
		{
			using (var rnd = new RNGCryptoServiceProvider())
			{
				var saltBytes = new byte[256];
				rnd.GetNonZeroBytes(saltBytes);
				return Convert.ToBase64String(saltBytes);
			}
		}

		static string ComputeHash(string password, string salt)
		{

			var saltBytes = Convert.FromBase64String(salt);
			var plainTextBytes = Encoding.UTF8.GetBytes(password);


			var plainTextWithSaltBytes = new byte[plainTextBytes.Length + saltBytes.Length];

			plainTextBytes.CopyTo(plainTextWithSaltBytes, 0);
			saltBytes.CopyTo(plainTextWithSaltBytes, plainTextBytes.Length);

			using (var hash = new SHA256Managed())
			{
				byte[] hashBytes = hash.ComputeHash(plainTextWithSaltBytes);
				string hashValue = Convert.ToBase64String(hashBytes);
				return hashValue;
			}
		}

		public Connection Connection
		{
			get { return m_Connection; }
		}


		public EditorModel LoadEditor(string userName)
		{
			var cmd = new ProcedureCall("EditorSelectModel");
			cmd.Add("UserName", userName);
			cmd.Add("Audit_UserKey", 0);
			var row = Connection.ExecuteRow(cmd);
			var result = new EditorModel(row);
			return result;
		}


		public EditorModel LoadEditorByEmailAddress(string emailAddress)
		{
			var cmd = new ProcedureCall("EditorSelectModel");
			cmd.Add("EmailAddress", emailAddress);
			cmd.Add("Audit_UserKey", 0);
			var row = Connection.ExecuteRow(cmd);
			var result = new EditorModel(row);
			return result;
		}


		public IList<EditorModel> FindEditorsByEmailAddress(string emailAddressPattern, int pageIndex, int pageSize, out int totalRecords)
		{
			throw new NotImplementedException();
		}

		public IList<EditorModel> FindEditorsByUserName(string userNamePattern, int pageIndex, int pageSize, out int totalRecords)
		{
			throw new NotImplementedException();
		}

		public IList<EditorModel> GetAllUsers(bool onlyEnabledUsers, int pageIndex, int pageSize, out int totalRecords)
		{
			throw new NotImplementedException();
		}
	}
}
